﻿using System.Threading.Tasks;
using K9Nano.Application.Dtos;
using K9Nano.Domain.Authentication;
using K9Nano.Domain.Caching;
using K9Nano.Domain.Entities;
using K9Nano.Exceptions;
using K9Nano.Mapping;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace K9Nano.Application;

public class CurdAppServiceBase<TEntity, TDbContext, TCreateDto, TEditDto, TItemDto, TQueryDto>(TDbContext db)
    : ICurdAppServiceBase<TEntity, TCreateDto, TEditDto, TItemDto, TQueryDto>
    where TEntity : class, IEntity
    where TDbContext : DbContext
    where TEditDto : IKeyDto
    where TQueryDto : PagedQueryDto<TEntity>
{
    public virtual async Task<TItemDto> CreateAsync(TCreateDto createDto, CancellationToken cancellation)
    {
        var entity = MappingHelper.Mapper.Map<TEntity>(createDto!);
        db.Add(entity);
        await db.SaveChangesAsync(cancellation);
        var item = MapToDto(entity);
        return item;
    }

    public virtual async Task<TItemDto?> GetAsync(long id, CancellationToken cancellation)
    {
        var entity = await db.Set<TEntity>().AsNoTracking().FirstOrDefaultAsync(a => a.Id.Equals(id), cancellation);
        if (entity is null) return default;
        var item = MapToDto(entity);
        return item;
    }

    public virtual async Task<TItemDto> UpdateAsync(TEditDto updateDto, CancellationToken cancellation)
    {
        var entity = await db.Set<TEntity>().FirstOrDefaultAsync(a => a.Id.Equals(updateDto.Id), cancellation) ?? throw FriendlyException.BadRequest($"ID为 {updateDto.Id} 的数据不存在");
        entity = MappingHelper.Mapper.Map(updateDto, entity);
        await db.SaveChangesAsync(cancellation);
        var item = MapToDto(entity);
        return item;
    }

    public virtual async Task<bool> DeleteAsync(long id, CancellationToken cancellation)
    {
        var entity = await db.Set<TEntity>().FirstOrDefaultAsync(a => a.Id.Equals(id), cancellation);
        if (entity is null) return true;
        db.Remove(entity);
        return await db.SaveChangesAsync(cancellation) > 0;
    }

    public virtual async Task<PagedResultDto<TItemDto>> QueryAsync(TQueryDto queryDto, CancellationToken cancellation)
    {
        var query = db.Set<TEntity>().AsNoTracking();
        var predicate = queryDto.GetPredicate();
        if (predicate is not null)
        {
            query = query.Where(predicate);
        }
        var total = await query.CountAsync(cancellation);
        if (total <= 0) return new PagedResultDto<TItemDto> { Total = 0, Items = [] };
        query = queryDto.Sort(query);
        var items = await query
            .Skip(queryDto.Skip())
            .Take(queryDto.PageSize)
            .ToListAsync(cancellation);
        var itemDtos = items.Select(MapToDto).ToList();
        return new PagedResultDto<TItemDto>
        {
            Total = total,
            Items = itemDtos
        };
    }

    public virtual async Task<IEnumerable<TItemDto>> GetAllAsync(CancellationToken cancellation)
    {
        var result = await db.Set<TEntity>().AsNoTracking().ToListAsync(cancellation);
        var items = MappingHelper.Mapper.Map<List<TItemDto>>(result);
        return items;
    }

    protected virtual TItemDto MapToDto(TEntity entity) => MapToDto<TEntity, TItemDto>(entity);

    protected virtual TTo MapToDto<TFrom, TTo>(TFrom entity)
    {
        var dto = MappingHelper.Mapper.Map<TTo>(entity!);

        if (dto is IAuditDto audit)
        {
            var cache = db.GetService<IUserCache>();
            if (audit.CreatedBy > 0)
            {
                audit.CreatorName = cache.GetName(audit.CreatedBy);
            }
            if (audit.UpdatedBy > 0)
            {
                audit.UpdaterName = cache.GetName(audit.UpdatedBy.Value);
            }
        }

        if(dto is IOrganizationDto org)
        {
            if (org.OrganizationId > 0)
            {
                var cache = db.GetService<IOrganizationUnitCache>();
                org.OrganizationName = cache.Get(org.OrganizationId.Value)?.DisplayName;
            }
        }

        return dto;
    }
}
